package vip.finance.makemoney.service.impl;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.promeg.pinyinhelper.Pinyin;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import vip.finance.makemoney.apiSpider.eastmoney.EastMoneyPushApi;
import vip.finance.makemoney.apiSpider.eastmoney.EastQtUListRep;
import vip.finance.makemoney.apiSpider.snowball.*;
import vip.finance.makemoney.apiSpider.tonghuashun.ThsEtfStockRes;
import vip.finance.makemoney.apiSpider.tonghuashun.TongHuaShunApi;
import vip.finance.makemoney.common.MarketCodeEnum;
import vip.finance.makemoney.common.ScheduleEnum;
import vip.finance.makemoney.common.vo.ScheduleVo;
import vip.finance.makemoney.config.annotation.DoSchedule;
import vip.finance.makemoney.entity.*;
import vip.finance.makemoney.mapper.ScheduleLogMapper;
import vip.finance.makemoney.mapper.ScheduleMapper;
import vip.finance.makemoney.service.IEtfStockService;
import vip.finance.makemoney.service.IStockService;
import vip.finance.makemoney.service.IUserStockService;
import vip.finance.makemoney.service.ScheduleService;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;

/**
 * 定时器实现
 *
 * @author yangbin
 * @since 2023-02-09
 */
@Slf4j
@Service
public class ScheduleServiceImpl extends ServiceImpl<ScheduleMapper, Schedule> implements ScheduleService {
    @Resource
    private IUserStockService userStockService;
    @Resource
    private IStockService stockService;
    @Resource
    private IEtfStockService etfStockService;
    @Resource
    private SnowStockApi snowStockApi;
    @Resource
    private TongHuaShunApi tongHuaShunApi;
    @Resource
    private RedisService redisService;
    @Resource
    private ScheduleLogMapper scheduleLogMapper;
    @Resource
    private SnowApi snowApi;
    @Resource
    private StockCnHkUsServiceImpl stockCnHkUsService;
    @Resource
    private EastMoneyPushApi eastMoneyPushApi;

    @Override
    public List<ScheduleVo> scheduleList() {
        return baseMapper.selectScheduleList();
    }

    @Override
    public void runSchedule(Integer scheduleId, String args) {
        Schedule schedule = getById(scheduleId);
        ScheduleLog scheduleLog = new ScheduleLog();
        scheduleLog.setScheduleId(schedule.getId());
        scheduleLog.setScheduleName(schedule.getName());
        scheduleLog.setRunning(0);
        scheduleLog.setEndTime(null);
        scheduleLog.setDoCount(0);
        scheduleLog.setAllCount(0);
        scheduleLog.setPercent(BigDecimal.ZERO);
        scheduleLog.setMsg("等待中...");
        scheduleLog.setStartTime(LocalDateTime.now());
        scheduleLog.setUpdateTime(LocalDateTime.now());
        scheduleLogMapper.insert(scheduleLog);
        Integer scheduleLogId = scheduleLog.getId();
        schedule.setLastTaskId(scheduleLogId);
        updateById(schedule);
        new Thread(() -> {
            scheduleLog(scheduleLogId, 1, "运行中...");
            try {
                ReflectUtil.invoke(this, schedule.getMethod(), scheduleLogId, args);
            } catch (Exception e) {
                log.info("运行异常:", e);
                scheduleLog(scheduleLogId, 2, "运行异常...");
            }
        }).start();
    }

    /**
     * 更新股票库
     *
     * @param scheduleLogId 异步id
     * @param args          "CN", "HK", "US"
     */
    @DoSchedule(value = ScheduleEnum.SYNC_ALL_STOCK)
    @Override
    public void syncAllStock(Integer scheduleLogId, String args) {
        StringBuilder msg = new StringBuilder();
        for (String category : args.split(",")) {
            int page = 1;
            String msgInfo = "";
            while (true) {
                SnowScreenResult snowScreenResult = snowApi.screenerScreen(category, "", "", "symbol",
                        "US".equals(category) ? "5000_999999999" : "", page++, 100, 0);
                if (snowScreenResult.getData() != null && !CollectionUtils.isEmpty(snowScreenResult.getData().getList())) {
                    List<SnowScreenResult.DataBean.ListBean> beanList = snowScreenResult.getData().getList();
                    List<String> symbolList = beanList.stream().map(SnowScreenResult.DataBean.ListBean::getSymbol).toList();
                    List<Stock> updateStockList = stockService.list(Wrappers.<Stock>lambdaQuery().in(Stock::getCode, symbolList));
                    List<Stock> insertStockList = new ArrayList<>();
                    for (SnowScreenResult.DataBean.ListBean snow : beanList) {
                        Stock stock = updateStockList.stream().filter(x -> x.getCode().equals(snow.getSymbol())).findAny().orElse(null);
                        if (stock == null) {
                            stock = new Stock();
                            insertStockList.add(stock);
                            stock.setCreateTime(LocalDateTime.now());
                        }
                        stock.setCode(snow.getSymbol());
                        stock.setMarket(category);
                        String shortCode = Arrays.stream(Pinyin.toPinyin(snow.getName(), ",").split(","))
                                .map(s -> String.valueOf(s.length() > 0 ? s.charAt(0) : StrUtil.EMPTY).toLowerCase(Locale.ROOT))
                                .collect(Collectors.joining());
                        stock.setShortCode(shortCode.length() > 20 ? shortCode.substring(0, 20) : shortCode);
                        stock.setName(snow.getName());
                        stock.setCurrent(snow.getCurrent());
                        stock.setPct(snow.getPct());
                        stock.setUpdateTime(LocalDateTime.now());
                    }
                    if (!updateStockList.isEmpty()) {
                        stockService.updateBatchById(updateStockList);
                    }
                    if (!insertStockList.isEmpty()) {
                        stockService.saveBatch(insertStockList);
                    }
                    msgInfo = "已更新" + ((page - 2) * 100 + beanList.size()) + "条(" + category + ")";
                    scheduleLog(scheduleLogId, 1, "运行中，" + msgInfo);

                } else {
                    break;
                }
            }
            msg.append(msgInfo);
        }
        scheduleLog(scheduleLogId, 2, "运行结束。" + msg);
    }

    //更新ETF库
    @DoSchedule(value = ScheduleEnum.SYNC_ALL_ETF)
    @Override
    public void syncAllETF(Integer scheduleLogId, String args) {
        int count = 0;
        //18ETF 19LOF
        for (Integer type : List.of(18, 19)) {
            String typeDesc = type == 18 ? "ETF" : "LOF";
            int page = 1;
            while (true) {
                SnowEtfRes etfRes = snowStockApi.listEtf(type, 1, "desc", "followers", page++, 100);
                if (etfRes.getData() != null && !CollectionUtils.isEmpty(etfRes.getData().getList())) {
                    List<SnowEtfRes.DataBean.ListBean> beanList = etfRes.getData().getList();
                    List<String> symbolList = beanList.stream().map(SnowEtfRes.DataBean.ListBean::getSymbol).toList();
                    List<Stock> updateStockList = stockService.list(Wrappers.<Stock>lambdaQuery().in(Stock::getCode, symbolList));
                    List<Stock> insertStockList = new ArrayList<>();
                    for (SnowEtfRes.DataBean.ListBean snow : beanList) {
                        Stock stock = updateStockList.stream().filter(x -> x.getCode().equals(snow.getSymbol())).findAny().orElse(null);
                        if (stock == null) {
                            stock = new Stock();
                            insertStockList.add(stock);
                            stock.setCreateTime(LocalDateTime.now());
                        }
                        stock.setCode(snow.getSymbol());
                        stock.setMarket(typeDesc);
                        String shortCode = Arrays.stream(Pinyin.toPinyin(snow.getName(), ",").split(","))
                                .map(s -> String.valueOf(s.length() > 0 ? s.charAt(0) : StrUtil.EMPTY).toLowerCase(Locale.ROOT))
                                .collect(Collectors.joining());
                        stock.setShortCode(shortCode.length() > 20 ? shortCode.substring(0, 20) : shortCode);
                        stock.setName(snow.getName());
                        stock.setCurrent(snow.getCurrent());
                        stock.setPct(snow.getPercent());
                        stock.setUpdateTime(LocalDateTime.now());
                    }
                    if (!updateStockList.isEmpty()) {
                        stockService.updateBatchById(updateStockList);
                    }
                    if (!insertStockList.isEmpty()) {
                        stockService.saveBatch(insertStockList);
                    }
                    count += beanList.size();
                    scheduleLog(scheduleLogId, 1, "运行中，已更新" + count + "条(" + typeDesc + ")");
                } else {
                    break;
                }
            }
        }
        scheduleLog(scheduleLogId, 2, "运行结束。已更新" + count + "条(ETF+LOF)");
    }

    //更新ETF持仓
    @DoSchedule(value = ScheduleEnum.SYNC_ALL_ETF_STOCK)
    @Override
    public void syncAllETFStock(Integer scheduleLogId, String args) {
        scheduleLog(scheduleLogId, 1, "运行中...");
        LambdaQueryWrapper<Stock> queryWrapper = Wrappers.<Stock>lambdaQuery().eq(Stock::getMarket, "ETF");
        if (StringUtils.hasLength(args)) {
            queryWrapper.in(Stock::getCode, Arrays.stream(args.split(",")).collect(Collectors.toList()));
        }
        List<Stock> etfList = stockService.list(queryWrapper);
        for (int i = 0; i < etfList.size(); i++) {
            Stock stock = etfList.get(i);
            //etf代码
            String etfCode = stock.getCode();
            //同花顺持仓api
            log.info("同花顺持仓api:" + etfCode);
            ThsEtfStockRes etfStockResRes;
            try {
                etfStockResRes = tongHuaShunApi.listEtfStock(100, etfCode.startsWith("SZ") ? 36 : 20, 1, etfCode.substring(2));
            } catch (Exception e) {
                e.printStackTrace();
                log.info("同花顺持仓api:" + etfCode + "超时异常，已忽略并继续（同花顺接口不稳定）");
                continue;
            }
            if (etfStockResRes.getData() != null && !CollectionUtils.isEmpty(etfStockResRes.getData().getList())) {
                List<ThsEtfStockRes.DataBean.ListBean> beanList = etfStockResRes.getData().getList();
                List<String> symbolList = beanList.stream().map(ThsEtfStockRes.DataBean.ListBean::getCode).toList();

                List<EtfStock> insertStockList = new ArrayList<>();
                for (ThsEtfStockRes.DataBean.ListBean bean : beanList) {
                    EtfStock etfStock = new EtfStock();
                    if (bean.getMarket().startsWith("HK")) {
                        //港
                        etfStock.setStockCode(bean.getCode().substring(2));
                    } else if (bean.getMarket().equals(MarketCodeEnum.M_33.code)) {
                        //沪
                        etfStock.setStockCode(MarketCodeEnum.M_33.market + bean.getCode());
                    } else if (bean.getMarket().equals(MarketCodeEnum.M_17.code)) {
                        //深
                        etfStock.setStockCode(MarketCodeEnum.M_17.market + bean.getCode());
                    } else if (bean.getMarket().equals(MarketCodeEnum.M_185.code) || bean.getMarket().equals(MarketCodeEnum.M_169.code)) {
                        //美
                        etfStock.setStockCode(bean.getCode());
                    } else {
                        etfStock.setStockCode(bean.getCode());
                    }
                    etfStock.setEtfCode(etfCode);
                    etfStock.setStockName(bean.getName());
                    etfStock.setWeight(bean.getRate());
                    insertStockList.add(etfStock);
                }
                etfStockService.remove(Wrappers.<EtfStock>lambdaQuery().eq(EtfStock::getEtfCode, etfCode));
                if (!insertStockList.isEmpty()) {
                    etfStockService.saveBatch(insertStockList);
                }
            }
            scheduleLog(scheduleLogId, 1, "运行中，已更新" + (i + 1) + "/" + etfList.size() + "条ETF持仓明细");
        }
        scheduleLog(scheduleLogId, 2, "运行结束。已更新" + etfList.size() + "条ETF持仓明细");
    }

    /**
     * 更新进度
     *
     * @param scheduleLogId
     * @param running       0-等待运行，1-运行， 2结束
     * @param msg
     */
    private void scheduleLog(Integer scheduleLogId, int running, String msg) {
        ScheduleLog scheduleLog = scheduleLogMapper.selectById(scheduleLogId);
        scheduleLog.setRunning(running);
        scheduleLog.setMsg(msg);
        scheduleLog.setUpdateTime(LocalDateTime.now());
        if (running == 2) {
            scheduleLog.setEndTime(LocalDateTime.now());
            Schedule schedule = getById(scheduleLog.getScheduleId());
            schedule.setLastRunTime(LocalDateTime.now());
            updateById(schedule);
        }
        scheduleLogMapper.updateById(scheduleLog);
    }

    @Override
    public void refreshUserStockBySnow(String... markets) {
        for (String market : markets) {
            List<String> codes = userStockService.getAllUserStockCodes(market);
            if (!codes.isEmpty()) {
                refreshStocks(codes);
            }
        }
    }

    @Override
    public void refreshAllStockBySnow(String... markets) {
        for (String market : markets) {
            List<String> codes = stockService.list(Wrappers.<Stock>lambdaQuery().select(Stock::getCode).eq(Stock::getMarket, market)).stream().map(Stock::getCode).collect(Collectors.toList());
            for (List<String> subCodes : ListUtil.partition(codes, 100)) {
                refreshStocks(subCodes);
            }
        }
    }

    @Override
    public void refreshHKUS(Integer scheduleLogId, String args) {
        //清空
        stockCnHkUsService.remove(Wrappers.emptyWrapper());

        int count = 0;
        //含有-S，-SW等带-的港股
        List<Stock> hkList = stockService.list(Wrappers.<Stock>lambdaQuery().eq(Stock::getMarket, "HK").like(Stock::getName, "-"));
        for (Stock hkStock : hkList) {
            //对应美股
            List<Stock> usList = stockService.list(Wrappers.<Stock>lambdaQuery().eq(Stock::getMarket, "US").eq(Stock::getName, hkStock.getName().split("-")[0]));
            if (usList.size() == 0 && hkStock.getName().contains("集团")) {
                usList = stockService.list(Wrappers.<Stock>lambdaQuery().eq(Stock::getMarket, "US").eq(Stock::getName, hkStock.getName().replace("集团", "").split("-")[0]));
            }
            if (usList.size() == 1) {
                StockCnHkUs stockCnHkUs = new StockCnHkUs();
                stockCnHkUs.setCnCode("");
                stockCnHkUs.setHkCode(hkStock.getCode());
                stockCnHkUs.setUsCode(usList.get(0).getCode());
                stockCnHkUs.setCreateTime(LocalDateTime.now());
                stockCnHkUsService.save(stockCnHkUs);
                count++;
                scheduleLog(scheduleLogId, 1, "运行中...已更新" + count + "条港美股关系");
            }
        }
        scheduleLog(scheduleLogId, 2, "运行结束。已更新" + count + "条港美股关系");
    }

    @Override
    public void refreshZhiShu() {
        String secids = "0.399006,0.399001,1.000001,"
                + "133.USDCNH," //离岸人民币
                + "100.NDX,"
                + "100.SPX,"
                + "100.DJIA,"
                + "124.HSTECH,100.HSI";
        EastQtUListRep eastQtUListRep = eastMoneyPushApi.ulist("f2,f3,f4,f12",
                2,
                secids);
        for (EastQtUListRep.DataDTO.DiffDTO diffDTO : eastQtUListRep.getData().getDiff()) {
            SnowRealTimeResult.DataBean.ItemsBean itemsBean = new SnowRealTimeResult.DataBean.ItemsBean();
            itemsBean.setQuote(new SnowRealTimeResult.DataBean.ItemsBean.QuoteBean());
            itemsBean.setMarket(new SnowRealTimeResult.DataBean.ItemsBean.MarketBean());
            itemsBean.getMarket().setStatus_id(0);
            itemsBean.getMarket().setStatus("指数");
            itemsBean.getQuote().setCurrent(diffDTO.getF2());
            itemsBean.getQuote().setPercent(diffDTO.getF3());
            itemsBean.getQuote().setChg(diffDTO.getF4());
            itemsBean.getQuote().setCurrent_ext(BigDecimal.ZERO);
            itemsBean.getQuote().setChg_ext(BigDecimal.ZERO);
            itemsBean.getQuote().setPercent_ext(BigDecimal.ZERO);
            redisService.saveRealTime(diffDTO.getF12(), itemsBean);
        }
    }

    /**
     * 刷新行情
     *
     * @param codes
     */
    private void refreshStocks(List<String> codes) {
        //获取实时行情
        SnowRealTimeResult result = snowStockApi.realTime(String.join(",", codes), "detail");
        result.getData().getItems().stream().filter(x -> x.getQuote() != null).forEach(item -> redisService.saveRealTime(item.getQuote().getSymbol(), item));
    }
}
